🦀 Бэктестинг стратегий на Rust: от идеи до продакшн-системы


На главную > Блог > Категория > 🦀 Бэктестинг стратегий на Rust: от идеи до продакшн-системы

 rust_backtesting

Вступление: почему Rust меняет правила игры в бэктестинге

Вы когда-нибудь запускали бэктест на Python и уходили пить кофе, потому что ждать приходилось по 10 минут? А может, вы пробовали оптимизировать стратегию с помощью GridSearch и поняли, что на перебор 1000 комбинаций параметров уйдёт целый день? Это не ваша вина. Это ограничения языка.

Rust — язык, который работает на скорости C++ с безопасностью памяти, недостижимой для большинства других языков. В контексте бэктестинга это означает: миллионы свечей обрабатываются за секунды, а не минуты; большой объём данных перебирается с нулевыми затратами на сборку мусора; алгоритмы линейной сложности действительно остаются линейными на практике.

В этой статье я расскажу о лучших инструментах для бэктестинга на Rust, покажу примеры кода из реальных проектов и объясню, почему Rust — это выбор профессионалов, когда речь идёт о серёзных объёмах данных и высокочастотной торговле.

«Python — для прототипов и исследований. Rust — для продакшена и бэктестов на терабайтах данных. Скорость решает всё, когда каждый день на счету».

1. Почему Rust для бэктестинга? (Speed matters)

Бэктестинг — это вычислительно интенсивная задача. Чем больше исторических данных вы обрабатываете, чем сложнее ваша стратегия, тем дольше вы ждёте результат. Rust решает эту проблему на уровне языка.

Что даёт Rust для бэктестинга:

  • Компиляция в машинный код — никакого JIT, никаких интерпретаторов, только быстрый нативный код.
  • 🛡️ Zero-cost abstractions — высокоуровневый код не платит за удобство производительностью.
  • 🧵 Rayon для параллельных вычислений — одна строчка кода превращает последовательный перебор параметров в многопоточный.
  • 📊 SIMD-инструкции — векторизация вычислений для обработки массивов данных.
  • 💾 Минимальное потребление памяти — никакого GC, предсказуемые аллокации, работа с памятью как в C++.
📊 Реальные цифры (из бенчмарков RaptorBT): На 1000 свечей Rust-движок показывает ускорение в 5800 раз по сравнению с VectorBT. На 50000 свечей — в 25 раз [citation:5]. Это не «небольшое преимущество», это переход в другую весовую категорию.

Для трейдера это означает:

  • ✅ Вы можете тестировать стратегии на годах минутных данных за секунды.
  • ✅ Вы можете перебирать тысячи комбинаций параметров без ночных сессий.
  • ✅ Вы можете запускать бэктесты в реальном времени на потоковых данных.

2. Обзор экосистемы: лучшие Rust-библиотеки для бэктестинга

ПроектЧто делаетКлючевая особенностьДля кого
Kronos Бэктест-фреймворк с WebAssembly-стратегиями Поддержка WebAssembly — стратегии на любом языке, наносекундная точность [citation:3] HFT-трейдеры, кросс-языковые команды
RaptorBT Drop-in замена для VectorBT с PyO3-биндингами Полная совместимость с Python, ускорение до 5800x, 33 метрики [citation:5] Quant-разработчики, переходящие с Python
Nanobook Исполнение Python-стратегий через Rust-ядро Детерминированное исполнение LOB (6 млн оп/сек), интеграция с IBKR/Binance [citation:4] От исследования к продакшену, bridge между Python и Rust
QuantForge CLI-инструмент с детерминированным движком Decimal-арифметика, валидация данных, SQLite-хранение [citation:7] CLI-first трейдеры, ценящие воспроизводимость
Rustrade Event-driven экосистема для live- и бэктестинга Многопоточная архитектура на Tokio, поддержка Binance/IBKR/Hyperliquid [citation:10] Профессиональная live-торговля с бэктестами
🎯 Выбор инструмента:
  • Хотите остаться в Python, но получить скорость Rust? → RaptorBT [citation:5]
  • Пишете стратегию в браузере или на TypeScript? → Kronos (WASM) [citation:3]
  • Нужен мост между research и production? → Nanobook [citation:4]
  • Любите CLI и детерминизм? → QuantForge [citation:7]
  • Строите полноценную торговую систему? → Rustrade [citation:10]

3. Практический пример: RaptorBT — бэктестинг SMA-стратегии из Python

RaptorBT — идеальный выбор для тех, кто хочет получить скорость Rust, не переписывая весь код на новый язык. Это drop-in замена для VectorBT с полностью совместимыми метриками [citation:5].


import numpy as np
import pandas as pd
import raptorbt

# 1. Подготовка данных (тот же Pandas DataFrame, что и в VectorBT)
df = pd.read_csv("btc_data.csv", index_col=0, parse_dates=True)

# 2. Генерация сигналов (SMA crossover)
sma_fast = df['close'].rolling(10).mean()
sma_slow = df['close'].rolling(20).mean()
entries = (sma_fast > sma_slow) & (sma_fast.shift(1) <= sma_slow.shift(1))
exits = (sma_fast < sma_slow) & (sma_fast.shift(1) >= sma_slow.shift(1))

# 3. Настройка бэктеста
config = raptorbt.PyBacktestConfig(
    initial_capital=100000,      # начальный капитал
    fees=0.001,                  # 0.1% комиссия
    slippage=0.0005,             # 0.05% проскальзывание
    upon_bar_close=True
)

# 4. Добавление стоп-лоссов и тейк-профитов
config.set_fixed_stop(0.02)     # 2% stop-loss
config.set_fixed_target(0.04)    # 4% take-profit

# 5. Запуск бэктеста (вся тяжелая работа — в Rust-ядре)
result = raptorbt.run_single_backtest(
    timestamps=df.index.astype('int64').values,
    open=df['open'].values,
    high=df['high'].values,
    low=df['low'].values,
    close=df['close'].values,
    volume=df['volume'].values,
    entries=entries.values,
    exits=exits.values,
    direction=1,      # 1 = Long, -1 = Short
    weight=1.0,
    symbol="BTC",
    config=config,
)

# 6. Анализ результатов
print(f"Total Return: {result.metrics.total_return_pct:.2f}%")
print(f"Sharpe Ratio: {result.metrics.sharpe_ratio:.2f}")
print(f"Max Drawdown: {result.metrics.max_drawdown_pct:.2f}%")
print(f"Win Rate: {result.metrics.win_rate_pct:.2f}%")
print(f"Total Trades: {result.metrics.total_trades}")

# 7. Получение кривой капитала и трейдов
equity = result.equity_curve()
trades = result.trades()
⚡ Производительность: На 50000 свечей RaptorBT выполняет бэктест за 1.68 мс, в то время как VectorBT — за 43 мс. Разница колоссальная, особенно при оптимизации параметров (Grid Search) [citation:5].

4. Архитектура: Event-driven бэктестер на Rust

Большинство профессиональных бэктестеров используют событийно-ориентированную архитектуру. Она позволяет точно симулировать исполнение ордеров, проскальзывание и управление капиталом [citation:1].

Пример базового бэктестера из проекта rust-learning-for-trading [citation:1]:


use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone)]
pub struct Bar {
    pub time: DateTime<Utc>,
    pub open: f64,
    pub high: f64,
    pub low: f64,
    pub close: f64,
    pub volume: f64,
}

#[derive(Debug, Clone)]
pub enum OrderType {
    Market,
    Limit(f64),
    Stop(f64),
}

#[derive(Debug, Clone)]
pub struct Order {
    pub id: u64,
    pub symbol: String,
    pub side: Side,
    pub order_type: OrderType,
    pub quantity: f64,
    pub timestamp: DateTime<Utc>,
}

#[derive(Debug, Clone)]
pub enum Event {
    Bar(Bar),
    OrderFilled(Order),
    OrderPlaced(Order),
    TradeOpen(Trade),
    TradeClosed(Trade),
    PositionUpdated { symbol: String, quantity: f64, avg_price: f64 },
}

pub struct BacktestEngine {
    events: Vec<Event>,
    portfolio: Portfolio,
    strategy: Box<dyn Strategy>,
}

impl BacktestEngine {
    pub fn run(&mut self, data: &[Bar]) -> BacktestResult {
        for bar in data {
            // Генерируем событие новой свечи
            self.events.push(Event::Bar(bar.clone()));
            
            // Стратегия генерирует сигналы на основе событий
            let signals = self.strategy.on_event(&self.events, &self.portfolio);
            
            // Обрабатываем сигналы в ордера
            for signal in signals {
                let order = self.create_order(signal, bar);
                self.events.push(Event::OrderPlaced(order.clone()));
                
                // Симуляция исполнения (с учётом комиссий и проскальзывания)
                let fill_price = self.calculate_fill_price(order.order_type, bar);
                self.events.push(Event::OrderFilled(order));
                
                // Обновляем портфель
                self.portfolio.update(order, fill_price);
            }
        }
        
        self.calculate_metrics()
    }
}

5. Детерминизм и воспроизводимость: почему это важно

Одна из главных проблем бэктестинга на Python — недетерминированность. Перемещение индексов в Pandas, нестабильность хешей словарей, порядок итераций по множествам — всё это приводит к тому, что один и тот же бэктест может давать чуть разные результаты при каждом запуске.

Rust решает эту проблему на уровне языка и библиотек [citation:4][citation:5][citation:7]:

  • QuantForge хранит временные метки как UTC epoch, использует decimal арифметику, а не float, и исполняет ордера по правилу «сигнал на закрытии свечи → исполнение на открытии следующей» [citation:7].
  • Nanobook реализует детерминированное исполнение через event replay: одни и те же входные данные дают идентичный порядок сделок, одинаковые трейды и один и тот же путь портфеля [citation:4].
  • RaptorBT не использует JIT-компиляцию, устраняя вариативность между запусками [citation:5].
⚠️ Почему это критично для трейдера: Если вы не можете воспроизвести бэктест, вы не можете доверять его результатам. Детерминизм — это не опция, это требование для серьёзной работы.

6. Параллельная оптимизация параметров с Rayon

Когда ваша стратегия готова, следующий шаг — оптимизация параметров. В Rust это делается просто и элегантно с помощью библиотеки Rayon, которая превращает последовательные итерации в многопоточные [citation:5].


use rayon::prelude::*;

#[derive(Clone)]
struct StrategyParams {
    fast_period: usize,
    slow_period: usize,
    stop_loss_pct: f64,
}

fn backtest_single(params: &StrategyParams, data: &Data) -> f64 {
    // Бэктест с заданными параметрами, возвращает итоговую доходность
    // ...
}

fn optimize_parameters(data: &Data) -> StrategyParams {
    let fast_periods = 5..=20;
    let slow_periods = 20..=50;
    let stop_losses = [0.01, 0.02, 0.03, 0.05];
    
    // Создаём все комбинации параметров (всего ~15000)
    let params: Vec<StrategyParams> = fast_periods.flat_map(|fast| 
        slow_periods.flat_map(|slow| 
            stop_losses.iter().map(move |&sl| StrategyParams {
                fast_period: fast,
                slow_period: slow,
                stop_loss_pct: sl,
            })
        )
    ).collect();
    
    // Параллельный бэктест всех комбинаций
    let results: Vec<(StrategyParams, f64)> = params.par_iter()
        .map(|p| (p.clone(), backtest_single(p, data)))
        .collect();
    
    // Находим лучшие параметры
    results.into_iter()
        .max_by(|(_, r1), (_, r2)| r1.partial_cmp(r2).unwrap())
        .map(|(p, _)| p)
        .unwrap()
}
⚡ Результат: На 8-ядерном процессоре ускорение близко к линейному (~7-8 раз). 15000 бэктестов, которые в однопоточном режиме заняли бы 150 секунд, выполняются за 20 секунд.

7. Интеграция с Python: лучшее из двух миров

Не обязательно переписывать всё на Rust. Современный подход — использовать Rust для вычислительного ядра, а Python для высокоуровневой логики, визуализации и анализа.

PyO3 — мост между Rust и Python

PyO3 позволяет вызывать Rust-код из Python с минимальными накладными расходами. Все ведущие библиотеки (RaptorBT, Nanobook, Fundcloud) используют этот подход [citation:2][citation:4][citation:5].


# Пример использования Nanobook из Python
import nanobook

# Создаём портфель и запускаем бэктест
result = nanobook.backtest_weights(
    weight_schedule=[...],    # Python-логика расчёта весов
    price_schedule=[...],     # исторические данные
    initial_cash=1_000_000,
    cost_bps=15,
    stop_cfg={"trailing_stop_pct": 0.05},
)

# Все расчёты — в Rust, GIL отпущен
print(f"Sharpe: {result['metrics'].sharpe:.2f}")
print(f"Max DD: {result['metrics'].max_drawdown:.1%}")
🎯 Преимущества такого подхода:
  • ✅ Python остаётся для исследования данных и высокоуровневой логики.
  • ✅ Rust занимается тяжёлыми вычислениями (симуляция портфеля, LOB, метрики).
  • ✅ GIL отпущен — многопоточность работает на полную.
  • ✅ Ошибки типов ловятся на этапе компиляции Rust, а не в рантайме Python.

8. Сравнительная таблица: Python vs Rust для бэктестинга

ХарактеристикаPython (VectorBT, Backtrader)Rust (RaptorBT, Nanobook, Rustrade)
Скорость (1000 свечей) ~1460 мс (с JIT-прогревом) ~0.25 мс
Скорость (50000 свечей) ~43 мс ~1.7 мс
Сборка мусора Да, непредсказуемые паузы Нет, детерминированное управление памятью
Размер библиотеки (диск) ~450 MB <10 MB
Параллельные вычисления Ограничены GIL, сложно Из коробки (Rayon, Tokio)
Детерминизм Нет (из-за хешей, индексов) Да (строгие правила исполнения)

Заключение: Rust — следующий уровень бэктестинга

Бэктестинг на Rust — это не хайп, а прагматичный выбор для тех, кто работает с большими объёмами данных, сложными стратегиями или высокочастотной торговлей. Ускорение в десятки и сотни раз, детерминизм, безопасная многопоточность и предсказуемое управление памятью — не просто маркетинговые лозунги, а реальные преимущества, которые конвертируются в более быстрые итерации, более глубокую оптимизацию и, в конечном счёте, в лучшие торговые решения.

Начните с малого: установите RaptorBT, запустите бэктест вашей стратегии на Rust-движке, сравните скорость. Затем попробуйте оптимизировать параметры с Rayon. Потом, если почувствуете вкус, погрузитесь глубже — изучите Kronos, Nanobook или QuantForge. Экосистема Rust для бэктестинга растёт, и сейчас идеальное время, чтобы в неё войти.

И помните: даже самый быстрый бэктестер не спасёт от плохой стратегии. Но он даст вам инструмент, который позволит тестировать идеи быстрее, чем конкуренты. А в трейдинге скорость итераций — это половина успеха.

«Python делает вещи возможными. Rust делает вещи быстрыми. На дистанции побеждает тот, у кого оба инструмента в арсенале».

 

Дата размещения статьи: 04-06-2026 в 11:16:40